package com.phiture.erp.produce.core.service.impl;

import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.yudao.module.infra.api.service.JobApi;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.phiture.erp.basic.api.pojo.resp.ErpMaterialResp;
import com.phiture.erp.basic.api.pojo.resp.ErpUserCustomizeConfigResp;
import com.phiture.erp.basic.api.service.ErpMaterialApi;
import com.phiture.erp.basic.api.service.ErpUserCustomizeConfigApi;
import com.phiture.erp.common.constants.ErpNoPrefixConstants;
import com.phiture.erp.common.constants.ErpUserCustomizeConfigKeyConstants;
import com.phiture.erp.common.enums.ErpAuditStatus;
import com.phiture.erp.common.enums.ErpMaterialAttrEnum;
import com.phiture.erp.common.enums.ErpStockRecordBizTypeEnum;
import com.phiture.erp.common.utils.ErpNoGenerator;
import com.phiture.erp.produce.common.enums.ErpProduceInBackFlushProcessResultEnum;
import com.phiture.erp.produce.common.enums.ErpProduceInBackFlushStatusEnum;
import com.phiture.erp.produce.core.pojo.vo.ErpProduceInPageReqVO;
import com.phiture.erp.produce.core.pojo.vo.ErpProduceInSaveReqVO;
import com.phiture.erp.produce.core.pojo.vo.ErpProductionTaskMaterialVO;
import com.phiture.erp.produce.core.pojo.vo.ProduceInEventSaveReqVO;
import com.phiture.erp.produce.core.service.ErpProduceInEventService;
import com.phiture.erp.produce.core.service.ErpProduceInService;
import com.phiture.erp.produce.core.service.ErpProductionTaskService;
import com.phiture.erp.produce.dal.entity.*;
import com.phiture.erp.produce.dal.mapper.*;
import com.phiture.erp.produce.dal.pojo.vo.ErpProduceInRespVO;
import com.phiture.erp.sale.api.pojo.resp.ErpSaleOrderItemResp;
import com.phiture.erp.sale.api.service.ErpSaleApi;
import com.phiture.erp.stock.api.pojo.req.ErpStockOutReq;
import com.phiture.erp.stock.api.pojo.req.ErpStockRecordCreateReq;
import com.phiture.erp.stock.api.service.ErpStockOutReqApi;
import com.phiture.erp.stock.api.service.ErpStockRecordApi;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;


import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static com.phiture.erp.common.constants.ErpUserCustomizeConfigKeyConstants.PRODUCE_IN_BACK_FLUSH_JOB_ID;
import static com.phiture.erp.common.constants.ErrorCodeConstants.*;

/**
 * 生产入库-包含成品，半成品 Service 实现类
 *
 * @author lekaihuai
 */
@Service
@Validated
@Slf4j
public class ErpProduceInServiceImpl implements ErpProduceInService {

    @Resource
    private ErpProduceInMapper produceInMapper;
    @Resource
    private ErpProduceInItemsMapper produceInItemsMapper;
    @Resource
    private ErpProductionTaskMapper productionTaskMapper;
    @Resource
    private ErpProduceMainPlanMapper produceMainPlanMapper;
    @Resource
    private ErpStockRecordApi stockRecordApi;
    private ErpProductionTaskService productionTaskService;

    @Resource
    private ErpProduceInEventService produceInEventService;

    @Resource
    private ProduceInEventItemsMapper produceInEventItemsMapper;
    @Resource
    private ErpNoGenerator erpNoGenerator;
    @Resource
    ErpSaleApi erpSaleApi;

    @Resource
    private ErpProductionTaskService erpProductionTaskService;
    @Resource
    private ErpMaterialApi erpMaterialApi;
    @Resource
    private ErpStockOutReqApi erpStockOutReqApi;
    @Resource
    private ErpUserCustomizeConfigApi erpUserCustomizeConfigApi;

    @Resource
    protected ApplicationEventPublisher applicationEventPublisher;

    @Resource
    JobApi jobApi;


    @Resource
    public void setProductionTaskService(ErpProductionTaskService productionTaskService) {
        this.productionTaskService = productionTaskService;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createProduceIn(ErpProduceInSaveReqVO createReqVO) {
        // 自动生成 生产入库 编号
        String produceInNo = erpNoGenerator.generate(ErpNoPrefixConstants.PRODUCE_IN_NO_PREFIX);
        validateMpNo(produceInNo);
        createReqVO.setProduceInNo(produceInNo);
        // 插入
        ErpProduceInDO produceIn = BeanUtils.toBean(createReqVO, ErpProduceInDO.class);
        produceInMapper.insert(produceIn);
        // 插入子表
        createProduceInItemsList(produceIn.getId(), createReqVO.getProduceInItemss());
        // 更新生产任务单的入库数量
        for(ErpProduceInItemsDO produceInItem : createReqVO.getProduceInItemss()){
            if(produceInItem.getSourceBillId() != null){
                updateRelationBillQty(produceInItem.getSourceBillId());;
            }
        }
        // 返回
        return produceIn.getId();
    }

    private void validateMpNo(String produceInNo){
        boolean exists = produceInMapper.exists(
                Wrappers.lambdaQuery(ErpProduceInDO.class)
                        .eq(ErpProduceInDO::getProduceInNo, produceInNo)
                        .eq(ErpProduceInDO::getDeleted, Boolean.FALSE)
        );
        if(exists){
            throw exception(PRODUCE_IN_NO_EXISTS);
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateProduceIn(ErpProduceInSaveReqVO updateReqVO) {
        // 校验存在
        validateProduceInExists(updateReqVO.getId());
        // 更新
        ErpProduceInDO updateObj = BeanUtils.toBean(updateReqVO, ErpProduceInDO.class);
        produceInMapper.updateById(updateObj);
        // 更新子表
        updateProduceInItemsList(updateReqVO.getId(), updateReqVO.getProduceInItemss());

        // 更新生产任务单的入库数量
        for(ErpProduceInItemsDO produceInItem : updateReqVO.getProduceInItemss()){
            updateRelationBillQty(produceInItem.getSourceBillId());
        }
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateProduceInStatus(Long id, Integer status, Long warehouseId, BigDecimal finalQty){

        boolean approve = ErpAuditStatus.APPROVE.getStatus().equals(status);
        // 1.1 校验存在
        ErpProduceInDO updateObj = validateProduceInExists(id);
        // 1.2 校验状态
        if (updateObj.getStatus().equals(status)) {
            throw exception(approve ? PRODUCE_IN_APPROVE_FAIL : PRODUCE_IN_PROCESS_FAIL);
        }
        // 2. 更新状态
        int updateCount = produceInMapper.updateByIdAndStatus(id, updateObj.getStatus(),
                new ErpProduceInDO().setStatus(status));
        if (updateCount == 0) {
            throw exception(approve ? PRODUCE_IN_APPROVE_FAIL : PRODUCE_IN_PROCESS_FAIL);
        }

        // 3. 变更库存
        // 3.1 查询生产入库明细
        List<ErpProduceInItemsDO> produceInItemsList = getProduceInItemsListByProduceInId(id);
        Integer bizType = approve ? ErpStockRecordBizTypeEnum.PRODUCE_IN.getType()
                : ErpStockRecordBizTypeEnum.PRODUCE_IN_CANCEL.getType();
        produceInItemsList.forEach(produceInItem -> {
            // 移动端 生产入库单明细的仓库ID可能为null，使用传入的仓库ID
            if (approve && warehouseId != null) {
                produceInItem.setWarehouseId(warehouseId);
                produceInItem.setFinalQty(finalQty);
                produceInItemsMapper.updateById(produceInItem);
            }

            BigDecimal count = approve ? produceInItem.getFinalQty() : produceInItem.getFinalQty().negate();
            stockRecordApi.createStockRecord(new ErpStockRecordCreateReq(
                    produceInItem.getMaterialId(), warehouseId == null ? produceInItem.getWarehouseId() : warehouseId, count,
                    bizType, produceInItem.getProduceInId(), produceInItem.getId(), updateObj.getProduceInNo()));

            // 如果生产入库单明细的原单据ID不为空，则更新销售订单的入库数量
            if(produceInItem.getSourceBillId() != null){
                ErpProductionTaskDO task = productionTaskMapper.selectById(produceInItem.getSourceBillId());
                // 根据生产任务的的主计划编码查询主计划
                if(task != null){
                    ErpProduceMainPlanDO produceMainPlan = produceMainPlanMapper.selectByNo(task.getProductMainPlanNo());
                    if (produceMainPlan == null) {
                        return ;
                    }
                    // 根据主计划的源单据ID，源单据行号，查询销售订单明细
                    ErpSaleOrderItemResp saleOrderItem = erpSaleApi.getSaleOrderItemById(produceMainPlan.getSourceDetailId());
                    if (saleOrderItem == null) {
                        return;
                    }
                    // 更新销售订单明细的已入库数量
                    saleOrderItem.setProduceInQty((saleOrderItem.getProduceInQty() == null ? BigDecimal.ZERO : saleOrderItem.getProduceInQty()).add(count));
                    // 更新销售订单明细
                    erpSaleApi.updateSaleOrderItem(BeanUtils.toBean(saleOrderItem, ErpSaleOrderItemResp.class));
                }
            }
        });

        // 发送一个事件进行倒冲处理
//        applicationEventPublisher.publishEvent(new ProduceInEvent(this, produceInItemsList, approve));
        // 倒冲处理改成事务处理，插入生产入库事务表，由定时任务进行倒冲处理
        produceInEventService.createProduceInEvent(new ProduceInEventSaveReqVO()
                .setProduceInId(id)
                .setProduceInNo(updateObj.getProduceInNo())
                .setInType(status)
                .setStatus(ErpProduceInBackFlushStatusEnum.WAIT_DOING.getStatus()));

        // 触发定时任务进行倒冲处理, 定时任务进行关闭
        try {
            jobApi.triggerJob(PRODUCE_IN_BACK_FLUSH_JOB_ID);
        } catch (Exception e) {
            // 触发定时任务失败，抛出异常
           log.error("触发定时任务失败", e);
        }

    }

    /**
     * 更新生产任务单的已入库数量
     *
     * @param id 生产任务单ID
     */
    private void updateRelationBillQty(Long id) {

        // 查出所有关联的生产入库明细
        List<ErpProduceInItemsDO> produceInItemsList = produceInItemsMapper.selectList(ErpProduceInItemsDO::getSourceBillId, id );
        // 查询对应的生产任务的入库数量 生产任务单没有明细，所有map应该只有一条数据
        Map<Long, BigDecimal> returnCountMap = produceInItemsMapper.selectProductionTaskCountSumMapByInIds(
                convertList(produceInItemsList, ErpProduceInItemsDO::getProduceInId));
        // 更新生产任务单的入库数量
        ErpProductionTaskDO task = productionTaskService.updateProductionTaskInCount(id, returnCountMap);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteProduceIn(Long id) {
        // 校验存在
        ErpProduceInDO produceIn = validateProduceInExists(id);

        // 校验状态
        if (produceIn.getStatus().equals(ErpAuditStatus.APPROVE.getStatus())) {
            throw exception(PRODUCE_IN_DELETE_FAIL_APPROVE);
        }
        // 删除
        produceInMapper.deleteById(id);
        // 查询子表
        List<ErpProduceInItemsDO> produceInItemsList = getProduceInItemsListByProduceInId(id);
        // 删除子表
        deleteProduceInItemsByProduceInId(id);
        // 更新生产任务单的入库数量
        for(ErpProduceInItemsDO produceInItem : produceInItemsList){
            if(produceInItem.getSourceBillId() != null){
                updateRelationBillQty(produceInItem.getSourceBillId());;
            }
        }
    }

    private ErpProduceInDO validateProduceInExists(Long id) {
        ErpProduceInDO result = produceInMapper.selectById(id);
        if (result == null) {
            throw exception(PRODUCE_IN_NOT_EXISTS);
        }
        return result;
    }

    @Override
    public ErpProduceInDO getProduceIn(Long id) {
        return produceInMapper.selectById(id);
    }

    @Override
    public PageResult<ErpProduceInRespVO> getProduceInPage(ErpProduceInPageReqVO reqVO) {

        IPage<ErpProduceInRespVO> pageResult =
                produceInMapper.selectMyPage(MyBatisUtils.buildPage(reqVO),
                        reqVO.getProduceInNo(),
                        reqVO.getMaterialName(),
                        reqVO.getStatus(),
                        TenantContextHolder.getTenantId());
        return new PageResult<>(pageResult.getRecords(), pageResult.getTotal());


    }

    // ==================== 子表（生产入库明细） ====================

    @Override
    public List<ErpProduceInItemsDO> getProduceInItemsListByProduceInId(Long produceInId) {
        return produceInItemsMapper.selectListByProduceInId(produceInId);
    }

    private void createProduceInItemsList(Long produceInId, List<ErpProduceInItemsDO> list) {
        list.forEach(o -> o.setProduceInId(produceInId));
        produceInItemsMapper.insertBatch(list);
    }

    private void updateProduceInItemsList(Long produceInId, List<ErpProduceInItemsDO> list) {
//        deleteProduceInItemsByProduceInId(produceInId);
		list.forEach(o -> o.setUpdater(null).setUpdateTime(null)); // 解决更新情况下：1）id 冲突；2）updateTime 不更新
//        createProduceInItemsList(produceInId, list);
        produceInItemsMapper.updateBatch(list);
    }

    private void deleteProduceInItemsByProduceInId(Long produceInId) {
        produceInItemsMapper.deleteByProduceInId(produceInId);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void doBackFlushWhenProduceIn(Long eventId,  List<ErpProduceInItemsDO> inItemsDOList, boolean approve) {
        inItemsDOList.forEach(inItemsDO -> {
            // 根据入库单明细的源单ID,查询对应的生产任务单
            ErpProductionTaskDO taskDO = productionTaskService.getProductionTask(inItemsDO.getSourceBillId());
            // 根据入库单明细的生产入库ID，查询对应的入库单
            ErpProduceInDO produceInDO = getProduceIn(inItemsDO.getProduceInId());
            // 根据生产任务单ID，查找对应的生产任务单物料清单表
            List<ErpProductionTaskMaterialVO> taskMaterialVOList = productionTaskService.getProductionTaskMaterialListByProductionTaskId(inItemsDO.getSourceBillId(), null);
            // 创建领料出库单
            ErpStockOutReq erpStockOutReq = buildErpStockOutReq(taskMaterialVOList, inItemsDO.getFinalQty(), taskDO, approve, produceInDO.getProduceDept());
            if(erpStockOutReq != null){
                long id = erpStockOutReqApi.createStockOutReq(erpStockOutReq);
                // 倒冲出库审批处理
                erpStockOutReqApi.updateStockOutReqStatus(id, ErpAuditStatus.APPROVE.getStatus());
                // 创建一条倒冲出库成功的明细至表produce_in_event_items
                produceInEventItemsMapper.insert(new ErpProduceInEventItemsDO()
                        .setParentId(eventId)
                        .setProduceInNo(produceInDO.getProduceInNo())
                        .setMaterialId(inItemsDO.getMaterialId())
                        .setProduceInItemId(inItemsDO.getId())
                        .setQty(approve ? inItemsDO.getFinalQty() : inItemsDO.getFinalQty().negate())
                        .setResult("倒冲成功")
                        .setResultCode("0000")
                );

            }
        });

        // 倒冲出库成功 更新事务处理状态
        ProduceInEventSaveReqVO produceInEventSaveReqVO = new ProduceInEventSaveReqVO();
        produceInEventSaveReqVO.setId(eventId);
        produceInEventSaveReqVO.setProcessResult(ErpProduceInBackFlushProcessResultEnum.SUCCESS.getStatus());
        produceInEventSaveReqVO.setStatus(ErpProduceInBackFlushStatusEnum.SUCCESS.getStatus());
        produceInEventService.updateProduceInEvent(produceInEventSaveReqVO);
    }

    private ErpStockOutReq buildErpStockOutReq(List<ErpProductionTaskMaterialVO> taskMaterialVOList, BigDecimal inQuantity,
                                               ErpProductionTaskDO taskDO,
                                               boolean approve, Long produceDeptId) {
        ErpStockOutReq erpStockOutReq = new ErpStockOutReq();
        // 出库类型
        erpStockOutReq.setOutType(approve ? ErpStockRecordBizTypeEnum.BACK_FLUSH_OUT.getType() : ErpStockRecordBizTypeEnum.BACK_FLUSH_OUT_CANCEL.getType());
        // 部门
        erpStockOutReq.setCustomerId(SecurityFrameworkUtils.getLoginUserDeptId());
        // 出库时间
        erpStockOutReq.setOutTime(LocalDateTime.now());
        // 创建者 设置成系统管理员
        erpStockOutReq.setCreator(1L);

        List<ErpStockOutReq.Item> items = new ArrayList<>();
        ErpUserCustomizeConfigResp erpSelfMadeInFlag = erpUserCustomizeConfigApi.getUserCustomizeConfig(
                ErpUserCustomizeConfigKeyConstants.ERP_SELF_MADE_IN_FLAG);
        taskMaterialVOList.forEach(taskMaterialVO -> {
            // 根据物料ID，查询对应的物料信息
            ErpMaterialResp materialResp = erpMaterialApi.getMaterial(taskMaterialVO.getMaterialId());
            // 首先倒冲标识必须为真
            // 如果物料属性为自制件，并且配置了自制件入库标志，则进行出库
            // 或者者物料属性为采购件，进行出库
            if(taskMaterialVO.isBackFlushFlag() &&
                    (
                    (ErpMaterialAttrEnum.SELF_MADE_PARTS.getAttr().equals(materialResp.getPlanInfo().getMaterialAttr())
                            && "true".equals(erpSelfMadeInFlag.getConfigValue()))
                            || (ErpMaterialAttrEnum.PURCHASED_PARTS.getAttr().equals(materialResp.getPlanInfo().getMaterialAttr()))
                    )){
                BigDecimal outQuantity = inQuantity.multiply(taskMaterialVO.getPlannedQuantity().divide(taskDO.getPlannedQuantity()));
                ErpStockOutReq.Item item = new ErpStockOutReq.Item();
                item.setWarehouseId(taskMaterialVO.getDefaultWarehouseId());
                item.setMaterialId(taskMaterialVO.getMaterialId());
                item.setCount(outQuantity);
                item.setSourceBillNo(taskDO.getProductionTaskNo());
                item.setSourceBillId(taskMaterialVO.getProductionTaskId());
                item.setSourceBillItemId(taskMaterialVO.getId());
                item.setCreator(1L); // 设置成系统管理员
                items.add(item);
            }
        });
        erpStockOutReq.setItems(items);
        if(items.size() == 0){
            return null;
        }else{
            return erpStockOutReq;
        }
    }

}